﻿//使用js模拟面向对象，支持如下功能：
//  1、创建类
//      oo('class', {
//          prop: 数组,
//          _类名: 函数,相当于构造函数,
//          方法1: 函数,
//          方法2: 函数
//      });
//      prop为该类的属性,可以使用如下语法定义：
//          1、字符串表示名字
//          2、对象，结构如下：
//              {
//                  name: 属性名,
//                  value: 默认值(可省略,但是当getter函数没有使用到value时,则需要给value设置null或undefined除外的默认值),
//                  getter: function(owner){
//                      owner为当前对象实例,this则为value
//                  },
//                  setter: function(value, owner){
//                      this(value);//当前this是有一个参数的函数，传入的参数会赋值给value
//                  },
//                  isStatic: bool//属性是否为静态，为静态的情况下只有一个单例
//              }
//              当设置setter、getter为null时，则表示该属性不能赋值或取值。
//      示例代码：
//          oo('class', {
//              prop: ['name', { name: 'age', value: 20 }],
//              _person: function(name){
//                  //相当于构造函数,将name的值赋给属性name,使用prop对属性进行赋值时,默认返回当前对象
//                  //因此可以继续调用对象的任意方法
//                  this.prop('name', name);
//              },
//              show: function(){
//                  alert('My name is ' + this.prop('name') + ', ' + this.prop('age') + ' years old.');
//              }
//          });
//      调用:
//          var p = oo('person', 'xiao ming');
//          p.show();
//          //My name is xiao ming, 20 years old.
//  2、继承
//      oo(':基类名', {
//          prop: 数组,可以忽略,则子类从父类继承属性
//          _子类名: 函数,如果要给父类构造函数传递默认参数,则要使用数组,数组格式为:[函数,参数1,参数2]
//          方法1: 函数,
//          方法2: 函数
//      });
//      示例代码：
//          oo(':person', {
//              prop: ['job'],
//              _XiaoY: [function(age, job){
//                  this.prop('age', age).prop('job', job);
//              }, 'XiaoY'],
//              show: function(){
//                  this.prop('base').show();
//                  alert('my job is ' + this.prop('job'));
//              }
//          });
//      调用:
//          var y = oo('XiaoY', 22, 'doctor');
//          y.show();
//          //My name is XiaoY, 22 years old.
//          //my job is doctor
(function () {
    Function.prototype.bind = function (obj) {
        var self = this;
        return function () {
            self.apply(obj, arguments);
        };
    };

    var m_base = {};
    var m_method;
    var m_class = {};
    var m_extend_reg = /^:(\w+)$/;
    var m_initializeList = {};
    var m_initialize = '$';

    window.oo = function () {
        var obj, instance;
        var args = Array.prototype.slice.call(arguments);
        var nm = args.shift();
        if ((obj = m_method[nm]) || (m_extend_reg.test(nm) && (args.splice(0, 0, RegExp.$1), obj = extend)))
            instance = obj.apply(null, args);
        else if ((m_base.hasOwnProperty(nm) && (obj = m_base[nm]())) ||
            (m_class.hasOwnProperty(nm) && (obj = createClass(nm)))) {
            instance = new obj();
            if (m_initializeList.hasOwnProperty(nm)) {
                m_initializeList[nm].call(instance);
                delete m_initializeList[nm];
            }
            obj.constructor.apply(instance, args);
        }
        return instance;
    };

    var m_class_prop = 'prop';
    m_method = {
        'class': function (infos) {
            var className, constructor;
            for (var k in infos) {
                if (isClassName(k) && (className = RegExp.$1)) {
                    (constructor = infos[k]) || (infos[k] = function () { });
                    oo('objEx', infos).add(m_class_prop, []);
                    infos[m_class_prop].push(createClassNameProp());
                    oo('objEx', infos).set(m_class_prop, parseProps(infos[m_class_prop]));
                    var initializeMethod = m_initialize + className;
                    if (infos.hasOwnProperty(initializeMethod)) {
                        m_initializeList[className] = infos[initializeMethod];
                        delete infos[initializeMethod];
                    }
                    m_class[className] = infos;
                    break;
                }
            }
        },
        clone: function (value) {
            if (Object === value.constructor) {
                var newObj = {};
                for (var k in value) {
                    newObj[k] = arguments.callee(value[k]);
                }
                return newObj;
            }
            else if (Array === value.constructor) {
                var newArr = [];
                for (var i = 0, item; item = value[i]; i++) {
                    newArr.push(arguments.callee(item));
                }
                return newArr;
            }
            return value;
        }
    };

    var m_prop_class_name = 'name';
    var m_prop_class_value = 'value';
    var m_prop_class_static = 'isStatic';
    var m_prop_class_set = 'setter';
    var m_prop_class_get = 'getter';
    function createClassNameProp() {
        var classNameProp = {};
        oo('objEx', classNameProp).add(m_prop_class_name, 'className')
			.add(m_prop_class_value, RegExp.$1)
			.add(m_prop_class_static, true)
			.add(m_prop_class_set, null);
        return classNameProp;
    };

    function parseProps(props) {
        var newProps = [];
        for (var i = 0, item; item = props[i]; i++) {
            if ('string' == typeof item) {
                var propInfos = {};
                propInfos[m_prop_class_name] = item;
                newProps.push(oo(m_class_prop, propInfos));
            }
            else if ('object' == typeof item) {
                newProps.push(m_class_prop === item.toString() ? item : oo(m_class_prop, item));
            }
        };
        return newProps;
    };

    m_base.objEx = function () {
        var obj = {};
        var ex = function () { };
        ex.constructor = function (o) {
            obj = o;
        };
        ex.prototype.set = function (name, value) {
            obj.hasOwnProperty(name) && (obj[name] = value);
            return this;
        };
        ex.prototype.get = function (name, defaultValue) {
            return obj.hasOwnProperty(name) ? obj[name] : defaultValue;
        };
        ex.prototype.add = function (name, value) {
            obj.hasOwnProperty(name) || (obj[name] = value);
            return this;
        };
        return ex;
    };

    m_base[m_class_prop] = function () {
        var newInfos = {};

        var obj = function () { };
        obj.constructor = function (infos) {
            if (null == oo('objEx', infos).get(m_prop_class_name, null)) {
                alert('属性必须包含:' + m_prop_class_name);
                return;
            }
            newInfos[m_prop_class_name] = infos[m_prop_class_name];

            newInfos[m_prop_class_value] = oo('objEx', infos).get(m_prop_class_value, null);

            var accessor;
            newInfos[m_prop_class_set] = infos.hasOwnProperty(m_prop_class_set) ?
                (null == (accessor = infos[m_prop_class_set]) ? function () { } : accessor) :
                function (newValue) { this(newValue); };

            newInfos[m_prop_class_get] = infos.hasOwnProperty(m_prop_class_get) ?
                (null == (accessor = infos[m_prop_class_get]) ? function () { return; } : accessor) :
                function () { return this; };
            newInfos[m_prop_class_static] = oo('objEx', infos).get(m_prop_class_static, false);
        };
        obj.prototype.name = function () {
            return newInfos.name;
        };
        obj.prototype.val = function (value, owner) {
            var isGetter = 'undefined' == typeof value;
            var accessor;
            if (null == (accessor = oo('objEx', newInfos).get(isGetter ? m_prop_class_get : m_prop_class_set, null)))
                return;
            if (isGetter) {
                return null == newInfos.value ? null : accessor.call(newInfos.value, owner);
            }
            else {
                accessor.call(function (val) {
                    newInfos.value = val;
                }, value, owner);
                return this;
            }
        };
        obj.prototype.clone = function () {
            if (newInfos.isStatic) return this;

            var o = m_base[m_class_prop]();
            o.constructor(newInfos);
            return new o();
        };
        obj.prototype.toString = function () {
            return m_class_prop;
        };
        return obj;
    };

    function createClass(className) {
        var infos = m_class[className];
        var newProps = cloneProps(oo('objEx', infos).get(m_class_prop, []));

        var obj = function () { };
        for (var k in infos) {
            isClassName(k) ? (obj.constructor = infos[k]) :
                (obj.prototype[k] = k === m_class_prop ? createPropertyAccessor(newProps) :
                    infos[k]);
        }
        return obj;
    };

    function cloneProps(props) {
        var newProps = [];
        for (var i = 0, prop; prop = props[i]; i++) {
            newProps.push(prop.clone());
        }
        return newProps;
    };

    var m_class_prop_base_class = 'base';
    function createPropertyAccessor(newProps) {
        return function (name, value) {
            var isGetter = 'undefined' == typeof value;
            for (var i = 0, prop; prop = newProps[i]; i++) {
                if (name === prop.name()) {
                    var returnValue = prop.val(value, this);
                    return isGetter ? parsePropertyValue(returnValue) : this;
                }
            }
            for (var i = 0, prop; prop = newProps[i]; i++) {
                if (m_class_prop_base_class === prop.name()) {
                    var returnValue = prop.val().prop.call(this, name, value);
                    return isGetter ? parsePropertyValue(returnValue) : this;
                }
            }
            return isGetter ? null : this;
        }
    };

    function parsePropertyValue(value) {
        if (value.constructor == String)
            return value.toString();
        if (value.constructor == Number)
            return parseInt(value.toString());
        if (value.constructor == Boolean)
            return value.toString().toLowerCase() == 'true';
        return value;
    };

    function extend(baseNm, childInfos) {
        oo('objEx', childInfos).add(m_class_prop, []).get(m_class_prop).push(
            oo(m_class_prop, { name: m_class_prop_base_class })
        );

        var baseInfos = m_class[baseNm];
        for (var k in baseInfos) {
            if (isClassName(k)) {
                for (var ck in childInfos) {
                    if (isClassName(ck)) {
                        childInfos[ck] = createExtendConstructor(baseNm, childInfos[ck]);
                        break;
                    }
                }
            }
            else {
                oo('objEx', childInfos).add(k, baseInfos[k]);
            }
        }
        childInfos[m_class_prop_base_class] = function () {
            var args = Array.prototype.slice.call(arguments);
            var nm = args.shift();
            return baseInfos.hasOwnProperty(nm) ? baseInfos[nm].apply(this, args) : this;
        };
        oo('class', childInfos);
    };

    function createExtendConstructor(baseName, childArgs) {
        var args = oo('clone', childArgs);
        var constructor;
        if (Array === args.constructor) {
            constructor = 1 < args.length ? args.shift() : null;
        }
        else {
            constructor = args;
            args = [];
        }
        null == constructor && (constructor = function () { });
        return function () {
            var newArgs = oo('clone', args);
            newArgs.push.apply(newArgs, arguments);
            var obj = createClass(baseName);
            var base = new obj;
            this.prop(m_class_prop_base_class, base);
            obj.constructor.apply(this, newArgs);
            constructor.apply(this, arguments);
        };
    };

    var m_class_name_reg = /^_(\w+)$/;
    function isClassName(name) {
        return m_class_name_reg.test(name);
    };
})();